#!/usr/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# Restricted Materials of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 2002 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 
#
######################################################################
#                                                                    #
# Module: addlcsext                                                  #
#                                                                    #
######################################################################
#                                                                    #
# Description: script to install CSM/Director integration            #
#              extensions on CSM management servers and nodes        #
#                                                                    #
# Input:                                                             #
#   -A : install Director Agent and CSM/Director agent               #
#   -C : install Director Console components                         #
#   -h : show usage                                                  #
#   -p : colon separated list of directories to locate CSM/Director  #
#        rpm files for install                                       #
#        (optional, default is $0:$0/reqs)                           #
#   -S : install Director Server components                          #
#   -v : verbose output                                              #
#                                                                    #
# Output: None                                                       #
#                                                                    #
# External references:                                               #
#         ksh                                                        #
#         rpm                                                        #
#         twgstart                                                   #
#         twgend                                                     #
#         twgstat                                                    #
#                                                                    #
# Return codes:                                                      #
#    0: Success                                                      #
#  1-4: Number of packages that failed to install                    #
#   96: Error with IBM Director operation                            #
#   97: RPM Error                                                    #
#   98: Missing pre-requisites                                       #
#   99: Parse Args Error                                             #
######################################################################
# sccsid = "@(#)94   1.3   src/csm/director/install/bin/addlcsext.perl, csm.director, csm_rpyxh, rpyxht1f3 8/12/02 15:42:29"

use locale;
use Getopt::Std;
use File::Basename;

##################
# Global variables
##################
$rcode = 0;                   # Script return code
$verbose = 0;                 # Verbose flag
$progname = basename($0);

$try_install_agent      = 0; # Install CSM/Director agent
$try_install_server     = 0; # Install CSM/Director server
$try_install_console    = 0; # Install CSM/Director console
$try_install_everything = 0; # Install all of above (default)

$agent_possible   = 0; # Prerequisites for csm.director.agent met
$console_possible = 0; # Prerequisites for csm.director.console met
$server_possible  = 0; # Prerequisites for csm.director.server met
$ITDAgent_possible= 0; # Need to install(=1) or upgrade(=2) ITDAgent

$TWG_DIR          = ""; # IBM Director installed directory
$director_running = 0;  # Status of Director before install

@search_path = ();  # Path to search for RPMS
%RPMS        = ();  # RPMS located   (keyed by name): version release file
%INSTALLED   = ();  # RPMS on system (keyed by name): version release


######################################################################
# usage:
#     display usage message
######################################################################
sub usage {
    print
      "$progname -h: Command usage syntax\n".
      "$progname [-v] [-A] [-C] [-S] [-p <path>[:<path>...]]\n".
      "\tInstall CSM integration extensions to IBM Director on CSM management servers and nodes\n".
      "\tThis will temporarily shut down any IBM Director agents or servers running\n".
      "\n".
      "-A : install IBM Director Agent and CSM Extensions to the agent\n".
      "-C : install CSM Extensions to IBM Director Console\n".
      "-S : install CSM Extensions to IBM Director Server\n".
      "-p : colon separated list of directories to locate RPM files for install\n".
      "     (Default is path $progname is invoked from and\n".
      "      the reqs subdirectory under that directory)\n".
      "-v : verbose output\n".
      "\n".
      "Default is -A -C -S\n";
}


######################################################################
# set_defaults:
#     display usage message and exit
######################################################################
use File::Spec;

sub set_defaults {
    if (defined($install_path)) {
        @search_path = split(/[ :]/, $install_path);
    } 
    elsif ($0 =~ m[/]) { # addlcsext invoked from explicit path
        my($base,$path,$suffix) = fileparse($0);
        # use catdir to strip off trailing / if there
        $search_path[0] = File::Spec->catdir($path);
        $search_path[1] = File::Spec->catdir($path, "reqs");
    }
    else { # addlcsext invoked from $PATH
        foreach $path (File::Spec->path()) {
            my($catted) = File::Spec->catfile($path, $0);
            if (-x File::Spec->catfile($path, $0)) {
                $search_path[0] = $path;
                $search_path[1] = File::Spec->catdir($path, "reqs");
                last;
            }
        }
    }

    if ($verbose) {
        print "RPM search path:\n";
        print join("\n", @search_path);
        print "\n\n";
    }

    # Replace this with more complicated logic (!) when we support more
    # platforms
    $rpmquery = "/bin/rpm --query";
    $queryfmt = "--queryformat \"%{NAME} %{VERSION} %{RELEASE} %{ARCH}\\n\"";

    $RPMARCH = "i386";
    $required_director_level = "4.10";
    $pkgs_to_query =
      "csm.core rsct.core.utils ".      # pre-reqs
      "ITDAgent ITDServer ITDConsole ". # ITD pre-reqs
      "csm.director.agent csm.director.console csm.director.server"; # Dir ext
    %common_prerequisites = ( "csm.core",        "1.3.0.0",
                              "rsct.core.utils", "2.3.0.0" );
}


######################################################################
# parse_args:
#     parse and verify command line arguments
######################################################################
sub parse_args {
  if (!getopts('AChp:Sv')) {
      &usage;
      exit(99);
  }

  if ($opt_h) { &usage; exit(0); }

  if ($opt_A) { if ($try_install_agent++  ) { &usage; exit(99); } }
  if ($opt_C) { if ($try_install_console++) { &usage; exit(99); } }
  if ($opt_S) { if ($try_install_server++ ) { &usage; exit(99); } }

  if ($opt_p) { $install_path = $opt_p; }
  if ($opt_v) { $verbose++; }

  if ($try_install_agent   == 0 &&
      $try_install_console == 0 &&
      $try_install_server  == 0) {
      # if no options specified, try everything
      $try_install_everything = 1;
  }
}


######################################################################
# determine_installed_sw:
#     locate/identify CSM/Director components already installed
######################################################################
sub determine_installed_sw {
  print "$rpmquery $pkgs_to_query $queryfmt\n" if $verbose;
  chomp(my(@rpmout)=`$rpmquery $pkgs_to_query $queryfmt`);
  my($rc) = $? >> 8;
  foreach $answer (@rpmout) {
      my($name,$version,$release,@rest) = split(/ /,$answer);
      print "name \"$name\" ".
            "version \"$version\" ".
            "release \"$release\" ".
            "rest \"@rest\"\n"
             if $verbose;
      if ($name ne "package") {
          $INSTALLED{$name} = [$version, $release];
      }
  }

  if ($verbose) {
      print "\nInstalled Packages:\n".
            "===================\n";
      for $pkg (keys %INSTALLED) {
          my($version,$release) = @{$INSTALLED{$pkg}};
          if ($version ne "") {
              print "$pkg $version $release\n";
          }
      }
      print "\n\n";
  }
}


######################################################################
# verifyPrerequisites
#     * Make sure required packages are installed
#     * Figure out what RPMS could be installed based on what
#       IBM Director packages are installed
######################################################################
sub verifyPrerequisites {
    my ($rc) = 0;
    for (($requirement,$reqv) = each(%common_prerequisites)) {
        if (exists $INSTALLED{$requirement}) {
            my ($v,$r) = @{$INSTALLED{$requirement}};
            if ($v lt $reqv) {
                print "$progname: Error - pre-requisite RPM $requirement is not at required level.\n\tInstalled: $v, Required: $reqv\n";
                $rc++;
            }
        }
        else {
            print "$progname: Error - missing pre-requisite RPM $requirement\n";
            $rc++;
        }
    }
    if ($rc != 0) { exit(98); }

    # Install csm.director.* if ITDServer exists at the right level
    if (exists $INSTALLED{"ITDServer"}) {
        my($version, $release) = @{$INSTALLED{"ITDServer"}};
        if ($version ge $required_director_level) {
            # ITDServer contains an agent, console, and server
            $agent_possible = 1;
            $console_possible = 1;
            $server_possible = 1;
        } elsif ($try_install_server) {
            print "$progname: ITDServer must be upgraded to v$required_director_level or later to use CSM Extensions.\n";
        }

        # If ITDServer installed, no need to check other components
        return;
    }

    # Install csm.director.console if ITDConsole installed at correct level
    if (exists $INSTALLED{"ITDConsole"}) {
        my($version, $release) = @{$INSTALLED{"ITDConsole"}};
        if ($version ge $required_director_level) {
            $console_possible = 1;
        } elsif ($try_install_console) {
            print "$progname: ITDConsole must be upgraded to v$required_director_level or later to use CSM Console Extensions.\n";
        }
    }

    # Install/upgrade ITDAgent if it was requested
    if ($try_install_everything || $try_install_agent) {
        my ($installITD);

        # Check if upgrade required
        if (exists $INSTALLED{"ITDAgent"}) {
            my($version, $release) = @{$INSTALLED{"ITDAgent"}};
            if ($version lt $required_director_level) {
                $installITD = 2;
            }
        }
        else { # Install required
            $installITD = 1;
        }

        if ($installITD) { # Something needs to be done
            if (exists $RPMS{"ITDAgent"}) { # Can we find the RPM?
                my ($v, $r, $file) = @{$RPMS{"ITDAgent"}};
                if ($v ge $required_director_level) { #Is it the right level?
                    $ITDAgent_possible = $installITD;
                }
                else { # RPM found, but backlevel
                    print "$progname: ITDAgent RPM v$required_director_level is required for CSM Extensions.\n".
                          "Highest available version is v$v.\n";
                    return;
                }
            }
            else { # Can't find RPM at all
                print "$progname: Unable to locate ITDAgent RPM v$required_director_level\n";
                return;
            }

            # ITDAgent and ITDConsole cannot co-exist unless forced to
            if (exists $INSTALLED{"ITDConsole"}) {
                $forceFlag = "--replacefiles";
            }
        }

        $agent_possible = 1;
    }
    else {
        # agent_possible is irrelevant because it is not requested
    }
}


######################################################################
# get_driector_status:
#     Determine if IBM director is running
#     side effect: sets TWG_DIR if it can be determined
######################################################################
sub get_director_status {
    # Determine location of IBM Director
    my($configFile);
    if (exists $INSTALLED{"ITDServer"}) {
        my($serverVer, $serverRel) = @{$INSTALLED{"ITDServer"}};
        if ($serverVer ge $required_director_level) {
            $configFile = "/etc/TWGserver/TWGserver";
        }
    }
    elsif (exists $INSTALLED{"ITDAgent"}) {
        my($agentVer,  $agentRel) = @{$INSTALLED{"ITDAgent"}};
        if ($agentVer ge $required_director_level) {
            $configFile = "/etc/TWGagent/TWGagent";
        }
    }
    else { return; }

    if (!open(TWGCONFIG, $configFile)) {
        print
          "Error: unable to open director configuration file $configFile\n";
    }
    else {
        my($line);
        while ($line = <TWGCONFIG>) {
            chomp($line);
            if ($line =~ /^\s*TWG_ROOTDIR\s*=\s*(\S+)\s*$/) {
                $TWG_DIR = $1;
            }
        }
    }
    close TWGCONFIG;

    if ($TWG_DIR eq "") {
        print "$progname: Unable to determine Director root directory\n";
        return;
    }

    print "Director root: $TWG_DIR\n" if $verbose;

    # Determine agent status
    print "$TWG_DIR/bin/twgstat\n" if $verbose;
    `/bin/ksh -c "$TWG_DIR/bin/twgstat > /dev/null 2>&1"`;
    my($rc) = $? >> 8;
    if ($rc == 0 || $rc == 1) {
        $director_running = 1;
    }
}


######################################################################
# stop_director
#     Stop director and wait for it to complete shutdown
######################################################################
sub stop_director {
    print "$progname: IBM Director Agent is running, stopping...\n";
    # Make sure agent is shut down before continuing
    print "$TWG_DIR/bin/twgend\n" if $verbose;
    `$TWG_DIR/bin/twgend`;
    my($rc) = 0;
    my($timeout_count) = 0;
    while ($rc != 3) {
        sleep(2);
        print "$TWG_DIR/bin/twgstat\n" if $verbose;
        `/bin/ksh -c "$TWG_DIR/bin/twgstat > /dev/null 2>&1"`;
        $rc = $? >> 8;
        print "twgstat rc=$rc\n" if $verbose;
        if ($timeout_count++ > 30) {
            print "$progname: Timeout stopping director server\n";
            exit(96);
        }
    }
}


######################################################################
# start_director
#     Start director agent
######################################################################
sub start_director {
    print "$progname: IBM Director Agent was running, restarting...\n";
    print "$TWG_DIR/bin/twgstart\n" if $verbose;
    `$TWG_DIR/bin/twgstart`;
}


######################################################################
# updateRPMKey
#     Determine if a given RPM is newer than previous versions located
######################################################################
sub updateRPMKey {
    my($key, $file) = @_;
    my($currentVersion, $currentRelease, $currentFile);

    # Query the characteristics of this package
    my($queryp) = "-p $file";
    print "$rpmquery $queryp $queryfmt\n" if $verbose;
    chomp(my($output) = `$rpmquery $queryp $queryfmt`);
    my(@rpmout) = split(/ /,$output);
    my($rc) = $? >> 8;
    if ($rc != 0) {
        return;
    }
    my($name,$version,$release,$arch,@rest) = @rpmout;
    print "name \"$name\" ".
          "version \"$version\" ".
          "release \"$release\" ".
          "arch \"$arch\" ".
          "rest \"@rest\"\n"
           if $verbose;

    # Sanity check: make sure RPM matches key
    if ($key ne $name) { return; }

    if ($name =~ /^ITD/) {
        if ($version lt $required_director_level) {
            return;
        }
    }

    # Make sure RPM is for the architecture we're interested in
    if ($arch ne $RPMARCH) { return; }

    # Compare with previous highest version found
    if (exists $RPMS{$key}) {
        ($currentVersion, $currentRelease, $currentFile) = @{$RPMS{$key}};
        if ($version lt $currentVersion) { return; }
        if ($version eq $currentVersion && $release lt $currentRelease) {
            return;
        }
    }

    # At this point, we have an RPM of the correct architecture
    # that has a higher version than the one we had

    # Add it to the hash
    $RPMS{$key} = [ $version, $release, $file];
}


######################################################################
# catalogRPMS
#     Locate all Director & csm.director rpms
######################################################################
sub catalogRPMS {
    my($sp);
    foreach $sp (@search_path) {
        if (!opendir(THEDIR, $sp)) {
            print "$progname: Warning: Unable to read search path directory \"$sp\"\n" if $verbose;
            next;
        }
        my(@files) = grep !/^\./, readdir THEDIR;
        my ($file);
        foreach $file (@files) {
            if ($file =~ /^(csm\.director\.(agent|console|server)).+\.rpm$/){
                my($fullpath) = File::Spec->catdir($sp, $file);
                updateRPMKey($1, $fullpath);
            }
            elsif ($file =~ /^(ITD(Agent|Console|Server)).+\.rpm$/) {
                my($fullpath) = File::Spec->catdir($sp, $file);
                updateRPMKey($1, $fullpath);
            }
        }
        closedir(THEDIR);
    }

    if ($verbose) {
      print "\nLocated RPMS:\n".
              "=============\n";
      my ($pkg);
      for $pkg (keys %RPMS) {
          my($version, $release, $file) = @{$RPMS{$pkg}};
          print "$pkg: $version, $release, $file\n";
      }
      print "\n\n";
  }
}


######################################################################
# inst_type
#     Determine if package is being installed or upgraded
######################################################################
sub inst_type {
    my($pkgname) = @_;

    if (exists $INSTALLED{$pkgname}) {
        my ($iv, $ir) = @{$INSTALLED{$pkgname}};
        my ($rv, $rr, $rfile) = @{$RPMS{$pkgname}};
        if ($iv lt $rv) {
            return "upgrade";
        }
        else {
            return "none";
        }
    }

    return "install";
}


######################################################################
# install_rpm
#     Install/Upgrade RPM file
######################################################################
sub install_rpm {
    my($pkgname, $filename, $install_type) = @_;

    # Figure out what is being installed:
    # File only -> get pkgname
    # Name only -> get file
    # both      -> assume they match
    # neither   -> error
    if ($filename ne "" && $pkgname eq "") { # Raw RPM file, get package
        my($queryp) = "-p $filename";
        print "$rpmquery $queryp $queryfmt\n" if $verbose;
        chomp(my(@rpmout)=`$rpmquery $queryp $queryfmt`);
        my($rc) = $? >> 8;
        if ($rc != 0) {
            return;
        }
        my(@rest);
        ($pkgname, @rest) = split(/ /,@rpmout);
    }
    elsif ($pkgname ne "" && $filename eq "") { # Package, get file
        if (exists $RPMS{$pkgname}) {
            my($version,$release,$fn) = @{$RPMS{$pkgname}};
            $filename = $fn;
        }
        else {
            print "$progname: Error - no RPM for package $pkgname available.\n";
            return 1;
        }
    }
    elsif ($pkgname eq "" && $filename eq "") { # bogus, nothing asked for
        print "$progname: Error - no package/file to $install_type\n";
        return 1;
    }

    if ($install_type eq "install" || $install_type eq "") {
        print "Installing $pkgname:\t";
        $install_type = "install";
    }
    elsif ($install_type eq "upgrade") {
        print "Upgrading $pkgname:\t";
    } else {
        print "install_rpm($pkgname, $filename, $install_type) error: invalid install type. Valid types are \"install\" and \"upgrade\"\n";
        return 0;
    }

    my ($inst_output);
    if ($forceFlag && $pkgname =~ /^ITD/) {
        print "\nrpm --$install_type $forceFlag $filename\n" if $verbose;
        $inst_output = `/bin/ksh -c "rpm --$install_type $forceFlag $filename 2>&1"`;
    }
    else {
        print "\nrpm --$install_type $filename\n" if $verbose;
        $inst_output = `/bin/ksh -c "rpm --$install_type $filename 2>&1"`;
    }

    my ($rc) = $? >> 8;
    if ($rc != 0) {
        print "Failed!\n$inst_output\n";
        return 1;
    }
    else {
        print "Succeeded!\n";
    }

    return 0;
}



######################################################################
######################################################################
# Mainline code
######################################################################
######################################################################

######################################################################
# Setup
######################################################################
&parse_args;   # parse command line
&set_defaults; # setup default values

# locate pre-req / other sw installed, packages to install
&determine_installed_sw;
&catalogRPMS;
&verifyPrerequisites;

if ($verbose) {
    print "ITDAgent:   $ITDAgent_possible\n".
          "agent:    $try_install_agent $agent_possible\n".
          "console:  $try_install_console $console_possible\n".
          "server:   $try_install_server $server_possible\n";
}

################################################
# Do the install
################################################
# Try to install IBM Director Agent
if ($ITDAgent_possible) {
    my ($itype);
    if ($ITDAgent_possible == 2) {
        &get_director_status;
        # Stop agent during install
        if ($director_running) {
            &stop_director;
        }
        $itype = "upgrade";
    }
    else {
        $itype = "install";
    }

    my ($rc) = &install_rpm("ITDAgent", "", $itype);
    if ($rc != 0) {
        $agent_possible = 0;
        $rcode++;
    }
    else {
        &determine_installed_sw; # Need to update installed list
    }
}

# Determine status of IBM Director daemons
#  This check needs to be here in case the ITDAgent install
#     starts the agent
#  Skip the check if this is an ITDAgent upgrade, because
#     we already ran this check above
if ($ITDAgent_possible != 2 &&
    (($agent_possible  && $try_install_agent ) ||
     ($server_possible && $try_install_server) ||
     (($agent_possible || $server_possible) && $try_install_everything))) {
    &get_director_status;
    # Stop agent during install
    if ($director_running) {
        &stop_director;
    }
}


# Try to install CSM Agent Extension
if ($try_install_everything || $try_install_agent) {
    if ($agent_possible) {
        my($inst_type) = &inst_type("csm.director.agent");
        if ($inst_type ne "none") {
            my($rc) = &install_rpm("csm.director.agent", "", $inst_type);
            if ($rc != 0) {
                $rcode++;
            }
        }
        elsif ($try_install_agent) {
            print "$progname: csm.director.agent already installed at latest available level.\n";
        }
    }
    elsif ($try_install_agent) {
        print "$progname: Error - Cannot install csm.director.agent: prerequistes are not met\n";
        $rcode++;
    }
}

# Try to install CSM Console Extension
if ($try_install_everything || $try_install_console) {
    if ($console_possible) {
        my($inst_type) = &inst_type("csm.director.console");
        if ($inst_type ne "none") {
            my($rc) = &install_rpm("csm.director.console", "", $inst_type);
            if ($rc != 0) {
                $rcode++;
            }
        }
        elsif ($try_install_console) {
            print "$progname: csm.director.console already installed at latest available level.\n";
        }
    }
    elsif ($try_install_console) {
        print "$progname: Error - Cannot install csm.director.console: prerequistes are not met\n";
        $rcode++;
    }
}

# Try to install CSM Server Extension
if ($try_install_everything || $try_install_server) {
    if ($server_possible) {
        my($inst_type) = &inst_type("csm.director.server");
        if ($inst_type ne "none") {
            my($rc) = &install_rpm("csm.director.server", "", $inst_type);
            if ($rc != 0) {
                $rcode++;
            }
        }
        elsif ($try_install_server) {
            print "$progname: csm.director.server already installed at latest available level.\n";
        }
    }
    elsif ($try_install_server) {
        print "$progname: Error - Cannot install csm.director.server: prerequistes are not met\n";
        $rcode++;
    }
}

################################################
# Clean up
################################################
# Restart agent if it was already running
if ($director_running) {
    &start_director;
}

exit $rcode;
